Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

27장. 벡터 DB 입문

이 장의 목표 RAG 규모가 커졌을 때 어떤 벡터 DB를 어떻게 골라야 하는지 큰 그림이 잡힙니다.

Chroma·Qdrant·LanceDB 셋만 알면 대부분의 사내 RAG는 만들 수 있습니다.


27.1 왜 벡터 DB가 필요한가

26장 코드는 100개 문서까지는 잘 동작합니다. 하지만 1만 개·10만 개로 가면?

  • 매번 모든 벡터를 다 비교 → 느려짐
  • 메모리에 다 못 올림
  • 새 문서 추가, 오래된 문서 삭제 어려움
  • 메타데이터 검색 안 됨

벡터 DB는 이걸 다 해줍니다.


27.2 벡터 DB의 4가지 일

  1. 저장 — 청크 + 벡터 + 메타데이터
  2. 검색 — 가장 가까운 N개 빠르게 찾기 (ANN 알고리즘)
  3. 필터 — “이 부서 문서만”, “최근 6개월” 등
  4. 갱신 — 문서 추가·삭제·재인덱싱

27.3 ANN — Approximate Nearest Neighbor

벡터 검색의 핵심 알고리즘 가족.

정확한 검색: 모든 벡터와 거리 계산 (느림, 정확)
ANN 검색: 약간의 근사를 허용해 100~1000배 빠름

대표 알고리즘:

  • HNSW (가장 흔함)
  • IVF + PQ
  • DiskANN

깊이 알 필요는 없습니다. 어떤 DB든 기본값으로 잘 동작합니다.


27.4 맥에서 쉽게 쓰는 벡터 DB 3가지

Chroma — 가장 입문 친화적

  • 파이썬 한 줄로 시작
  • SQLite 기반
  • 작은 ~ 중간 규모 (수십만 청크까지)
$ pip install chromadb
import chromadb

client = chromadb.PersistentClient(path="./db")
col = client.get_or_create_collection("docs")

col.add(
    documents=["문서 1 내용", "문서 2 내용"],
    metadatas=[{"src": "wiki"}, {"src": "회의록"}],
    ids=["d1", "d2"],
)

result = col.query(query_texts=["관련 질문"], n_results=2)

임베딩 모델을 안 지정하면 Chroma 기본 모델을 씀. 한국어 잘하려면 별도로 bge-m3 같은 걸 직접 임베딩 후 전달.

Qdrant — 사내 서버용 표준

  • Rust 기반, 매우 빠름
  • REST·gRPC API
  • 도커로 즉시 실행
  • 메타데이터 필터링 강력
  • 클라우드 서비스도 제공
$ docker run -p 6333:6333 -p 6334:6334 \
  -v $(pwd)/qdrant_storage:/qdrant/storage \
  qdrant/qdrant
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance

q = QdrantClient(host="localhost", port=6333)
q.recreate_collection(
    collection_name="docs",
    vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)

LanceDB — 임베디드 + 빠름

  • 파일 기반, 서버 필요 없음
  • Apache Arrow 기반
  • 빠른 쿼리·작은 디스크 풋프린트
  • 인덱싱 자동
$ pip install lancedb
import lancedb

db = lancedb.connect("./lance_db")
tbl = db.create_table("docs", data=[
    {"vector": [0.1, 0.2, ...], "text": "...", "src": "wiki"},
])
results = tbl.search([0.1, 0.2, ...]).limit(5).to_list()

27.5 세 DB 비교

항목ChromaQdrantLanceDB
진입 난이도매우 쉬움보통쉬움
규모~ 50만수억수백만
서버 필요옵션
메타 필터보통강력좋음
추천 사용처프로토타입사내 서비스임베디드

선택 가이드:

  • 처음 / 한 사람 / 노트북 → Chroma or LanceDB
  • 사내 서비스 / 다중 사용자 → Qdrant

27.6 청킹 전략 다시 보기 (실무 버전)

고정 길이 청킹

chunks = [text[i:i+500] for i in range(0, len(text), 400)]

500자 청크, 100자 겹침. 간단하지만 문장 중간에서 잘릴 수 있음.

문장·문단 기반

import re
sentences = re.split(r'(?<=[.!?])\s+', text)
chunks = []
buf = ""
for s in sentences:
    if len(buf) + len(s) < 500:
        buf += " " + s
    else:
        chunks.append(buf)
        buf = s

자연스러움 ↑

구조 기반 (Markdown / 코드)

  • 헤더 단위로 자르기
  • 코드는 함수 단위로

Recursive Splitter (LangChain 등)

여러 구분자(\n\n, \n, ., )를 순차로 시도하며 자릅니다. 가장 권장.


27.7 메타데이터 — RAG의 진짜 무기

벡터만 쓰지 말고 메타데이터 를 같이 저장하세요.

col.add(
    documents=["..."],
    metadatas=[{
        "source": "wiki",
        "title": "휴가 규정",
        "department": "HR",
        "last_updated": "2026-03-15",
        "language": "ko",
    }],
    ids=["d1"],
)

검색 때 필터:

result = col.query(
    query_texts=["휴가"],
    where={"department": "HR", "language": "ko"},
    n_results=3,
)

사내 검색이 잘 안 될 때 90%는 메타 필터 부재가 원인입니다.


27.8 Reranker — 한 단계 정밀화

벡터 검색은 빠르지만 종종 부정확. Reranker 모델이 1차 결과를 다시 정렬합니다.

[1차 벡터 검색] → 후보 20개
        ↓
[Reranker] → 상위 3~5개로 압축
        ↓
[LLM에 전달]

대표 모델:

  • BAAI/bge-reranker-v2-m3 (다국어)
  • jinaai/jina-reranker-v2-base-multilingual
# 예: sentence-transformers
from sentence_transformers import CrossEncoder
re = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [(query, c) for c in candidates]
scores = re.predict(pairs)

벡터 검색만 쓰는 RAG보다 체감 정확도가 크게 올라갑니다.


27.9 Hybrid Search — 벡터 + 키워드

벡터 검색이 약한 케이스:

  • 사람·코드 이름 (고유명사)
  • 짧은 키워드
  • 숫자·코드

이때는 BM25 같은 키워드 검색과 병합.

방법:

1. 벡터 검색 → top 20
2. 키워드 검색 → top 20
3. 점수를 정규화해서 합치기 (RRF 등)
4. Reranker로 5개 압축
5. LLM에 전달

이 조합이 현업 RAG의 표준.


27.10 인덱싱 시점 vs 검색 시점

  • 인덱싱: 문서를 청크화·임베딩해서 DB에 저장 → 느려도 OK (백그라운드)
  • 검색: 사용자 질문을 임베딩·검색·LLM 호출 → 가능한 한 빠르게

분리해서 설계하면 운영이 쉬워집니다.


27.11 RAG 운영 체크리스트

사내 RAG 도입 시 확인할 것.

  • ✅ 임베딩 모델은 한국어/다국어 강한 걸로 (bge-m3)
  • ✅ 청크 300~800자
  • ✅ 메타데이터 (source/department/date) 항상
  • ✅ Reranker 적용
  • ✅ “근거에 없으면 모른다” 시스템 프롬프트
  • ✅ 출처 표시 (chunk_id 또는 title)
  • ✅ 문서 갱신 파이프라인 (주기적 재인덱싱)
  • ✅ 평가 셋 — 정답 알려진 질문 30~50개로 회귀 테스트

이 장에서 기억할 한 가지

RAG의 품질은 LLM이 아니라 검색에서 결정됩니다.

  • 청킹 + 임베딩 + 메타 + Reranker 이 4박자가 RAG의 진짜 엔진입니다.

손으로 해볼 것

1. Chroma로 첫 RAG 만들기

$ pip install chromadb

26.9 절 코드를 Chroma로 옮겨 보세요. 같은 질문·같은 결과가 나와야 합니다.

2. 메타데이터 필터 실험

문서마다 category: "HR" / "ENG" / "FIN" 을 붙이고 질문에 카테고리 필터를 걸어서 검색해보세요.

3. Reranker 적용 (선택)

$ pip install sentence-transformers

27.8 코드를 그대로 적용해서 Reranker 적용 전후의 응답 정확도를 비교하세요.


다음 장에서는 Function Calling / Tool Use — 모델이 함수와 도구를 호출하게 만드는 패턴을 봅니다.

Agent의 전 단계입니다.